{/* Copyright 2020 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License. */}

import {Layout} from '@react-spectrum/docs';
export default Layout;

import docs from 'docs:@react-aria/slider';
import {HeaderInfo, FunctionAPI, TypeContext, InterfaceType, TypeLink, PageDescription} from '@react-spectrum/docs';
import packageData from '@react-aria/slider/package.json';
import statelyDocs from 'docs:@react-stately/slider';
import i18nDocs from 'docs:@react-aria/i18n';
import Anatomy from './anatomy.svg';
import ChevronRight from '@spectrum-icons/workflow/ChevronRight';

---
category: Forms
keywords: [slider, range, aria]
after_version: 3.0.0-alpha.3
---

# useSlider

<PageDescription>{docs.exports.useSlider.description}</PageDescription>

<HeaderInfo
  packageData={packageData}
  componentNames={['useSlider', 'useSliderThumb']}
  sourceData={[
    {type: 'W3C', url: 'https://www.w3.org/WAI/ARIA/apg/patterns/slider/'}
  ]} />

## API

<FunctionAPI function={docs.exports.useSlider} links={docs.links} />
<FunctionAPI function={docs.exports.useSliderThumb} links={docs.links} />

## Features

The [&lt;input type="range"&gt;](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/range)
HTML element can be used to build a slider, however it is
very difficult to style cross browser. `useSlider` and `useSliderThumb` help achieve accessible
sliders that can be styled as needed.

* Support for one or multiple thumbs
* Support for mouse, touch, and keyboard via the [useMove](../useMove) hook
* Multi-touch support for dragging multiple thumbs or multiple sliders at once
* Pressing on the track moves the nearest thumb to that position
* Supports using the arrow keys, as well as page up/down, home, and end keys
* Support for both horizontal and vertical orientations
* Support for custom min, max, and step values with handling for rounding errors
* Support for disabling the whole slider or individual thumbs
* Prevents text selection while dragging
* Exposed to assistive technology as a `group` of `slider` elements via ARIA
* Slider thumbs use hidden native input elements to support touch screen readers
* Support for labeling both the slider as a whole and individual thumbs
* Support for displaying the current thumb values using an `<output>` element
* Internationalized number formatting as a percentage or value
* Support for mirroring in RTL locales

## Anatomy

<Anatomy />

Sliders consist of a track element showing the range of available values,
one or more thumbs showing the current values, an
optional [&lt;output&gt;](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/output)
element displaying the current values textually, and a label.
The thumbs can be dragged to allow a user to change their value. In addition, the
track can be clicked to move the nearest thumb to that position.

### useSlider hook
`useSlider` returns several sets of props and you should spread each one onto the appropriate element:

<TypeContext.Provider value={docs.links}>
  <InterfaceType properties={docs.links[docs.exports.useSlider.return.id].properties} />
</TypeContext.Provider>

If there is no visual label, an `aria-label` or `aria-labelledby` prop must be passed instead
to identify the element to screen readers.

### useSliderThumb hook

`useSliderThumb` returns props that you should spread onto the appropriate elements, along with states for styling:

<TypeContext.Provider value={docs.links}>
  <InterfaceType properties={docs.links[docs.exports.useSliderThumb.return.id].properties} />
</TypeContext.Provider>

If there is no visual label, an `aria-label` or `aria-labelledby` prop must be passed instead
to identify each thumb to screen readers.

Slider state is managed by the <TypeLink links={statelyDocs.links} type={statelyDocs.exports.useSliderState} /> hook.

## Examples

### Single thumb

This example shows how to build a simple horizontal slider with a single thumb. In addition, it includes a label
which can be clicked to focus the slider thumb, and an `<output>` element to display the current slider value as
text. This is formatted using a locale aware number formatter provided by the [useNumberFormatter](../useNumberFormatter) hook.

The `<input>` element inside the thumb is used to represent the slider to assistive technology, and is hidden from view
using the [VisuallyHidden](../VisuallyHidden) component. The thumb also uses the [useFocusRing](../useFocusRing) hook to
display using a different color when it is keyboard focused (try tabbing to it).

```tsx example export=true
import {useSlider, useSliderThumb} from '@react-aria/slider';
import {useSliderState} from '@react-stately/slider';
import {useFocusRing} from '@react-aria/focus';
import {VisuallyHidden} from '@react-aria/visually-hidden';
import {mergeProps} from '@react-aria/utils';
import {useNumberFormatter} from '@react-aria/i18n';

function Slider(props) {
  let trackRef = React.useRef(null);
  let numberFormatter = useNumberFormatter(props.formatOptions);
  let state = useSliderState({...props, numberFormatter});
  let {
    groupProps,
    trackProps,
    labelProps,
    outputProps
  } = useSlider(props, state, trackRef);

  return (
    <div {...groupProps} className={`slider ${state.orientation}`}>
      {/* Create a container for the label and output element. */}
      {props.label &&
        <div className="label-container">
          <label {...labelProps}>{props.label}</label>
          <output {...outputProps}>
            {state.getThumbValueLabel(0)}
          </output>
        </div>
      }
      {/* The track element holds the visible track line and the thumb. */}
      <div {...trackProps} ref={trackRef} className={`track ${state.isDisabled ? 'disabled' : ''}`}>
        <Thumb index={0} state={state} trackRef={trackRef} name={props.name} />
      </div>
    </div>
  );
}

function Thumb(props) {
  let {state, trackRef, index, name} = props;
  let inputRef = React.useRef(null);
  let {thumbProps, inputProps, isDragging} = useSliderThumb({
    index,
    trackRef,
    inputRef,
    name
  }, state);

  let {focusProps, isFocusVisible} = useFocusRing();
  return (
    <div
      {...thumbProps}
      className={`thumb ${isFocusVisible ? 'focus' : ''} ${isDragging ? 'dragging' : ''}`}>
      <VisuallyHidden>
        <input ref={inputRef} {...mergeProps(inputProps, focusProps)} />
      </VisuallyHidden>
    </div>
  );
}

<Slider label="Opacity" />
```

<details>
  <summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Show CSS</summary>

```css
.slider {
  display: flex;
}

.slider.horizontal {
  flex-direction: column;
  width: 300px;
}

.slider.vertical {
  height: 150px;
}

.label-container {
  display: flex;
  justify-content: space-between;
}

.slider.horizontal .track {
  height: 30px;
  width: 100%;
}

/* track line */
.track:before {
  content: attr(x);
  display: block;
  position: absolute;
  background: gray;
}

.slider.horizontal .track:before {
  height: 3px;
  width: 100%;
  top: 50%;
  transform: translateY(-50%);
}

.slider.vertical .track {
  width: 30px;
  height: 100%;
}

.slider.vertical .track:before {
  width: 3px;
  height: 100%;
  left: 50%;
  transform: translateX(-50%);
}

.thumb {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: gray;
}

.thumb.dragging {
  background: dimgray;
}

.thumb.focus {
  background: orange;
}

.slider.horizontal .thumb {
  top: 50%;
}

.slider.vertical .thumb {
  left: 50%;
}

.track.disabled {
  opacity: 0.4;
}
```

</details>

### Multi thumb

This example shows how to build a slider with multiple thumbs. The thumb component is the same one shown in the previous
example. The main difference in this example is that there are two `<Thumb>` elements rendered with different `index` props.
In addition, the `<output>` element uses `state.getThumbValueLabel` for each thumb to display the selected range.

```tsx example export=true
function RangeSlider(props) {
  let trackRef = React.useRef(null);

  let numberFormatter = useNumberFormatter(props.formatOptions);
  let state = useSliderState({...props, numberFormatter});
  let {
    groupProps,
    trackProps,
    labelProps,
    outputProps
  } = useSlider(props, state, trackRef);

  return (
    <div {...groupProps} className={`slider ${state.orientation}`}>
      {props.label &&
        <div className="label-container">
          <label {...labelProps}>{props.label}</label>
          <output {...outputProps}>
            {`${state.getThumbValueLabel(0)} - ${state.getThumbValueLabel(1)}`}
          </output>
        </div>
      }
      <div {...trackProps} ref={trackRef} className={`track ${state.isDisabled ? 'disabled' : ''}`}>
        <Thumb index={0} state={state} trackRef={trackRef} />
        <Thumb index={1} state={state} trackRef={trackRef} />
      </div>
    </div>
  );
}

<RangeSlider
  label="Price Range"
  formatOptions={{style: 'currency', currency: 'USD'}}
  maxValue={500}
  defaultValue={[100, 350]}
  step={10} />
```

## Usage

The following examples show how to use the `Slider` and `RangeSlider` components created in the above examples.

### Vertical orientation

Sliders are horizontally oriented by default. The `orientation` prop can be set to `"vertical"` to create a vertical slider.
This example also uses `aria-label` rather than `label` to create a slider with no visible label.

```tsx example
<Slider
  orientation="vertical"
  aria-label="Opacity"
  maxValue={1}
  step={0.01} />
```

### Controlled value

The `value` prop paired with the `onChange` event can be used to make a slider controlled. The value must fall between the Slider's minimum and maximum values, which default to 0 and 100 respectively. The `onChange` event receives the new slider value as a parameter, which can be used to update state.

```tsx example
function Example() {
  let [value, setValue] = React.useState(25);
  return (
    <>
      <Slider
        label="Cookies to buy"
        value={value}
        onChange={setValue} />
      <p>Current value: {value}</p>
    </>
  );
}
```

Multi thumb sliders specify their values as an array rather than a single number.

```tsx example
function Example() {
  let [value, setValue] = React.useState([25, 75]);
  return (
    <>
      <RangeSlider
        label="Range"
        value={value}
        onChange={setValue} />
      <p>Current value: {value.join(' – ')}</p>
    </>
  );
}
```

### onChangeEnd

The `onChangeEnd` prop can be used to handle when a user stops dragging a slider, whereas the `onChange` prop is called as the user drags.

```tsx example
function Example() {
  let [value, setValue] = React.useState(25);
  return (
    <>
      <Slider
        label="Cookies to buy"
        defaultValue={value}
        onChangeEnd={setValue} />
      <p>Current value: {value}</p>
    </>
  );
}
```

### Custom value scale

By default, slider values are percentages between 0 and 100. A different scale can be used by setting the `minValue` and `maxValue` props.

```tsx example
<Slider
  label="Cookies to buy"
  minValue={50}
  maxValue={150}
  defaultValue={100} />
```

### Value formatting

Values are formatted as a percentage by default, but this can be modified by using the `formatOptions` prop to specify a different format.
`formatOptions` is compatible with the option parameter of [Intl.NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat) and is applied based on the current locale.

```tsx example
<Slider
  label="Currency"
  formatOptions={{style: 'currency', currency: 'JPY'}}
  defaultValue={60} />
```

### Step values

The `step` prop can be used to snap the value to certain increments. The steps are calculated
starting from the minimum. For example, if `minValue={2}`, and `step={3}`, the valid step values would be 2, 5, 8, 11, etc.
This example allows increments of 5 between 0 and 100.

```tsx example
<Slider
  label="Amount"
  formatOptions={{style: 'currency', currency: 'USD'}}
  minValue={0}
  maxValue={100}
  step={5} />
```

### Disabled

A slider can be disabled using the `isDisabled` prop.

```tsx example
<Slider
  label="Cookies to share"
  defaultValue={25}
  isDisabled />
```
### HTML forms

useSliderThumb supports the `name` prop for integration with HTML forms.

```tsx example
<Slider
  label="Opacity"
  defaultValue={50}
  name="opacity" />
```

## Internationalization

### Value formatting

Formatting the value that should be displayed in the value label or `aria-valuetext`
is handled by <TypeLink links={statelyDocs.links} type={statelyDocs.exports.useSliderState} />.
The formatting can be controlled using the `formatOptions` prop.
If you want to change locales, the <TypeLink links={i18nDocs.links} type={i18nDocs.exports.I18nProvider} />
must be somewhere in the hierarchy above the Slider.
This will tell the formatter what locale to use.

### RTL

In right-to-left languages, the slider should be mirrored. The label is right-aligned,
the value is left-aligned. Ensure that your CSS accounts for this.
